using System;
using System.Collections.Generic;
using System.Linq;
using SDF.Core;
using SDF.Core.Caching;
using SDF.Core.Data;
using SDF.Core.Domain.Catalog;
using SDF.Core.Domain.Projects;
using SDF.Services.Events;

namespace SDF.Services.Projects
{
    /// <summary>
    /// Project mapping service
    /// </summary>
    public partial class ProjectMappingService : IProjectMappingService
    {
        #region Constants

        /// <summary>
        /// Key for caching
        /// </summary>
        /// <remarks>
        /// {0} : entity ID
        /// {1} : entity name
        /// </remarks>
        private const string PROJECTMAPPING_BY_ENTITYID_NAME_KEY = "SDF.projectmapping.entityid-name-{0}-{1}";
        /// <summary>
        /// Key pattern to clear cache
        /// </summary>
        private const string PROJECTMAPPING_PATTERN_KEY = "SDF.projectmapping.";

        #endregion

        #region Fields

        private readonly IRepository<ProjectMapping> _projectMappingRepository;
        private readonly IProjectContext _projectContext;
        private readonly ICacheManager _cacheManager;
        private readonly IEventPublisher _eventPublisher;
        private readonly CatalogSettings _catalogSettings;

        #endregion

        #region Ctor

        /// <summary>
        /// Ctor
        /// </summary>
        /// <param name="cacheManager">Cache manager</param>
        /// <param name="projectContext">Project context</param>
        /// <param name="projectMappingRepository">Project mapping repository</param>
        /// <param name="catalogSettings">Catalog settings</param>
        /// <param name="eventPublisher">Event publisher</param>
        public ProjectMappingService(ICacheManager cacheManager, 
            IProjectContext projectContext,
            IRepository<ProjectMapping> projectMappingRepository,
            CatalogSettings catalogSettings,
            IEventPublisher eventPublisher)
        {
            this._cacheManager = cacheManager;
            this._projectContext = projectContext;
            this._projectMappingRepository = projectMappingRepository;
            this._catalogSettings = catalogSettings;
            this._eventPublisher = eventPublisher;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Deletes a project mapping record
        /// </summary>
        /// <param name="projectMapping">Project mapping record</param>
        public virtual void DeleteProjectMapping(ProjectMapping projectMapping)
        {
            if (projectMapping == null)
                throw new ArgumentNullException("projectMapping");

            _projectMappingRepository.Delete(projectMapping);

            //cache
            _cacheManager.RemoveByPattern(PROJECTMAPPING_PATTERN_KEY);

            //event notification
            _eventPublisher.EntityDeleted(projectMapping);
        }

        /// <summary>
        /// Gets a project mapping record
        /// </summary>
        /// <param name="projectMappingId">Project mapping record identifier</param>
        /// <returns>Project mapping record</returns>
        public virtual ProjectMapping GetProjectMappingById(int projectMappingId)
        {
            if (projectMappingId == 0)
                return null;

            return _projectMappingRepository.GetById(projectMappingId);
        }

        /// <summary>
        /// Gets project mapping records
        /// </summary>
        /// <typeparam name="T">Type</typeparam>
        /// <param name="entity">Entity</param>
        /// <returns>Project mapping records</returns>
        public virtual IList<ProjectMapping> GetProjectMappings<T>(T entity) where T : BaseEntity, IProjectMappingSupported
        {
            if (entity == null)
                throw new ArgumentNullException("entity");

            int entityId = entity.Id;
            string entityName = typeof(T).Name;

            var query = from sm in _projectMappingRepository.Table
                        where sm.EntityId == entityId &&
                        sm.EntityName == entityName
                        select sm;
            var projectMappings = query.ToList();
            return projectMappings;
        }


        /// <summary>
        /// Inserts a project mapping record
        /// </summary>
        /// <param name="projectMapping">Project mapping</param>
        public virtual void InsertProjectMapping(ProjectMapping projectMapping)
        {
            if (projectMapping == null)
                throw new ArgumentNullException("projectMapping");

            _projectMappingRepository.Insert(projectMapping);

            //cache
            _cacheManager.RemoveByPattern(PROJECTMAPPING_PATTERN_KEY);

            //event notification
            _eventPublisher.EntityInserted(projectMapping);
        }

        /// <summary>
        /// Inserts a project mapping record
        /// </summary>
        /// <typeparam name="T">Type</typeparam>
        /// <param name="projectId">Project id</param>
        /// <param name="entity">Entity</param>
        public virtual void InsertProjectMapping<T>(T entity, int projectId) where T : BaseEntity, IProjectMappingSupported
        {
            if (entity == null)
                throw new ArgumentNullException("entity");

            if (projectId == 0)
                throw new ArgumentOutOfRangeException("projectId");

            int entityId = entity.Id;
            string entityName = typeof(T).Name;

            var projectMapping = new ProjectMapping
            {
                EntityId = entityId,
                EntityName = entityName,
                ProjectId = projectId
            };

            InsertProjectMapping(projectMapping);
        }

        /// <summary>
        /// Updates the project mapping record
        /// </summary>
        /// <param name="projectMapping">Project mapping</param>
        public virtual void UpdateProjectMapping(ProjectMapping projectMapping)
        {
            if (projectMapping == null)
                throw new ArgumentNullException("projectMapping");

            _projectMappingRepository.Update(projectMapping);

            //cache
            _cacheManager.RemoveByPattern(PROJECTMAPPING_PATTERN_KEY);

            //event notification
            _eventPublisher.EntityUpdated(projectMapping);
        }

        /// <summary>
        /// Find project identifiers with granted access (mapped to the entity)
        /// </summary>
        /// <typeparam name="T">Type</typeparam>
        /// <param name="entity">Wntity</param>
        /// <returns>Project identifiers</returns>
        public virtual int[] GetProjectsIdsWithAccess<T>(T entity) where T : BaseEntity, IProjectMappingSupported
        {
            if (entity == null)
                throw new ArgumentNullException("entity");

            int entityId = entity.Id;
            string entityName = typeof(T).Name;

            string key = string.Format(PROJECTMAPPING_BY_ENTITYID_NAME_KEY, entityId, entityName);
            return _cacheManager.Get(key, () =>
            {
                var query = from sm in _projectMappingRepository.Table
                            where sm.EntityId == entityId &&
                            sm.EntityName == entityName
                            select sm.ProjectId;
                return query.ToArray();
            });
        }

        /// <summary>
        /// Authorize whether entity could be accessed in the current project (mapped to this project)
        /// </summary>
        /// <typeparam name="T">Type</typeparam>
        /// <param name="entity">Wntity</param>
        /// <returns>true - authorized; otherwise, false</returns>
        public virtual bool Authorize<T>(T entity) where T : BaseEntity, IProjectMappingSupported
        {
            return Authorize(entity, _projectContext.CurrentProject.Id);
        }

        /// <summary>
        /// Authorize whether entity could be accessed in a project (mapped to this project)
        /// </summary>
        /// <typeparam name="T">Type</typeparam>
        /// <param name="entity">Entity</param>
        /// <param name="projectId">Project identifier</param>
        /// <returns>true - authorized; otherwise, false</returns>
        public virtual bool Authorize<T>(T entity, int projectId) where T : BaseEntity, IProjectMappingSupported
        {
            if (entity == null)
                return false;

            if (projectId == 0)
                //return true if no project specified/found
                return true;

            if (_catalogSettings.IgnoreProjectLimitations)
                return true;

            if (!entity.LimitedToProjects)
                return true;

            foreach (var projectIdWithAccess in GetProjectsIdsWithAccess(entity))
                if (projectId == projectIdWithAccess)
                    //yes, we have such permission
                    return true;

            //no permission found
            return false;
        }

        #endregion
    }
}